package com.xc.stock.domain.basic.repository;

import java.io.Serializable;
import java.util.List;
import java.util.Optional;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.lang.Nullable;

import com.ag.utils.vo.KVContainer;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.querydsl.core.types.EntityPath;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Predicate;
import com.querydsl.jpa.impl.JPAQueryFactory;
import com.querydsl.jpa.impl.JPAUpdateClause;
import com.rp.spring.jpa.GenericJpaRepository;
import com.xc.stock.domain.basic.convert.IEntityPOConvert;
import com.xc.stock.utils.Beans;

public abstract class SimplePoConvertEntityRepository<E, P, ID extends Serializable> {

	public List<E> transform(List<P> l) {
		return convert().toEntity(l);
	}
	
	public List<E> findAll() {
		List<P> list = repo().findAll();
		return transform(list);
	}

	public ID id(P p) {
		JpaEntityInformation<P, ID> eif = repo().jpaEntityInformation();
		ID id = eif.getRequiredId(p);
		return id;
	}
	
	public List<E> findAll(Sort sort) {
		List<P> list = repo().findAll(sort);
		return transform(list);
	}

	public List<E> findAllById(Iterable<ID> ids) {
		List<P> list = repo().findAllById(ids);
		return transform(list);
	}

	public void deleteInBatch(Iterable<E> entities) {
		repo().deleteInBatch(Iterables.transform(entities, e -> convert().toPo(e)));
	}

	public E getOne(ID id) {
		P t = repo().getOne(id);
		return convert().toEntity(t);
	}

	public E save(E entity) {
		P po = convert().toPo(entity);
		repo().save(po);
		E newEntity = convert().toEntity(po);
		afterModifyEntity(newEntity, entity);
		return entity;
	}
	
	public E saveAndFlush(E entity) {
		P po = convert().toPo(entity);
		repo().saveAndFlush(po);
		E newEntity = convert().toEntity(po);
		afterModifyEntity(newEntity, entity);
		return entity;
	}
	
	void afterModifyEntity(E newEntity, E entity) {
		Beans.copyProperties(newEntity, entity, convert().ignoreProperties());
	}
	
	public List<E> saveAll(Iterable<E> entities) {
		List<KVContainer<E, P>> mList = Lists.newArrayList();
		
		List<P> list = Lists.newArrayList();
		entities.forEach(e -> {
			P po = convert().toPo(e);
			
			mList.add(new KVContainer<E, P>(e, po));
			list.add(po);
		});
		
		repo().saveAll(list);
		
		List<E> ret = Lists.newArrayList();
		for(KVContainer<E, P> entry : mList) {
			E newEntity = convert().toEntity(entry.getValue());
			E entity = entry.getKey();
			
			afterModifyEntity(newEntity, entity);
			ret.add(entity);
		}
		
		return ret;
	}

	public Optional<E> findById(ID id) {
		Optional<P> optional = repo().findById(id);
		return Optional.ofNullable(convert().toEntity(optional.orElse(null)));
	}
	
	public E find(ID id) {
		return findById(id).orElse(null);
	}
	
	public List<E> findById(Iterable<ID> ids) {
		List<P> list = repo().findAllById(ids);
		return transform(list);
	}

	public boolean existsById(ID id) {
		return repo().existsById(id);
	}

	public long count() {
		return repo().count();
	}

	public void deleteById(ID id) {
		repo().deleteById(id);
	}

	public void delete(E e) {
		repo().delete(convert().toPo(e));
	}

	public void deleteAll(Iterable<? extends E> entities) {
		repo().deleteAll(Iterables.transform(entities, e -> convert().toPo(e)));
	}

	public Optional<E> findOne(Predicate predicate) {
		Optional<P> optional = repo().findOne(predicate);
		return Optional.ofNullable(convert().toEntity(optional.orElse(null)));
	}

	public long count(Predicate predicate) {
		return repo().count(predicate);
	}

	public boolean exists(Predicate predicate) {
		return repo().exists(predicate);
	}

	public List<E> findAll(Predicate predicate, int maxResult) {
		List<P> list = repo().findAll(predicate, maxResult);
		return transform(list);
	}
	
	public List<E> findAll(Predicate predicate, int maxResult, OrderSpecifier<?>... orders) {
		List<P> list = repo().findAll(predicate, maxResult, orders);
		return transform(list);
	}

	public List<E> findAll(Predicate predicate) {
		List<P> list = repo().findAll(predicate);
		return transform(list);
	}

	public List<E> findAll(Predicate... predicate) {
		List<P> list = repo().findAll(predicate);
		return transform(list);
	}

	public List<E> findAll(Predicate predicate, Sort sort) {
		List<P> list = repo().findAll(predicate, sort);
		return transform(list);
	}

	public List<E> findAll(Predicate predicate, OrderSpecifier<?>... orders) {
		List<P> list = repo().findAll(predicate, orders);
		return transform(list);
	}

	public List<E> findAll(OrderSpecifier<?>... orders) {
		List<P> list = repo().findAll(orders);
		return transform(list);
	}

	public long count(Predicate... predicate) {
		return repo().count(predicate);
	}

	public Page<E> findAll(Predicate predicate, Pageable pageable) {
		Page<P> page = repo().findAll(predicate, pageable);
		return new PageImpl<>(transform(page.getContent()), page.getPageable(), page.getTotalElements());
	}
	
	public Page<E> findAll(Pageable pageable) {
		Page<P> page = repo().findAll(pageable);
		return new PageImpl<>(transform(page.getContent()), page.getPageable(), page.getTotalElements());
	}

	public Page<E> findAll(Predicate predicate, Pageable pageable, OrderSpecifier<?>... sorts) {
		Page<P> page = repo().findAll(predicate, pageable, sorts);
		return new PageImpl<>(transform(page.getContent()), page.getPageable(), page.getTotalElements());
	}

	public E findOneIfMutil(Predicate... predicate) {
		P t = repo().findOneIfMutil(predicate);
		return convert().toEntity(t);
	}
	
	public void setStatusEnable(ID id) {
		repo().setStatusEnable(id);
	}
	
	/**
	 * 设置实体.status的状态为 禁用 状态
	 */
	public void setStatusDisable(ID id) {
		repo().setStatusDisable(id);
	}
	
	public void setStatusValue(ID id, Integer status) {
		repo().setStatusValue(id, status);
	}
	
	//~==================================================== JpaSpecificationExecutor 实现


	/**
	 * Returns a single entity matching the given {@link Specification} or {@link Optional#empty()} if none found.
	 *
	 * @param spec can be {@literal null}.
	 * @return never {@literal null}.
	 * @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one entity found.
	 */
	public Optional<E> findOne(@Nullable Specification<P> spec) {
		Optional<P> optional = repo().findOne(spec);
		return Optional.ofNullable(convert().toEntity(optional.orElse(null)));
	}

	/**
	 * Returns all entities matching the given {@link Specification}.
	 *
	 * @param spec can be {@literal null}.
	 * @return never {@literal null}.
	 */
	public List<E> findAll(@Nullable Specification<P> spec) {
		List<P> list = repo().findAll(spec);
		return transform(list);
	}

	/**
	 * Returns a {@link Page} of entities matching the given {@link Specification}.
	 *
	 * @param spec can be {@literal null}.
	 * @param pageable must not be {@literal null}.
	 * @return never {@literal null}.
	 */
	public Page<E> findAll(@Nullable Specification<P> spec, Pageable pageable) {
		Page<P> page = repo().findAll(spec, pageable);
		Page<E> map = page.map(t -> convert().toEntity(t));
		return map;
	}

	/**
	 * Returns all entities matching the given {@link Specification} and {@link Sort}.
	 *
	 * @param spec can be {@literal null}.
	 * @param sort must not be {@literal null}.
	 * @return never {@literal null}.
	 */
	public List<E> findAll(@Nullable Specification<P> spec, Sort sort) {
		List<P> list = repo().findAll(spec, sort);
		return transform(list);
	}

	/**
	 * Returns the number of instances that the given {@link Specification} will return.
	 *
	 * @param spec the {@link Specification} to count instances for. Can be {@literal null}.
	 * @return the number of instances.
	 */
	public long count(@Nullable Specification<P> spec) {
		return repo().count(spec);
	}

	public JPAQueryFactory getJPAQueryFactory() {
		return repo().getJPAQueryFactory();
	}
	
	public JPAUpdateClause update(EntityPath<?> path) {
		return repo().update(path);
	}
	
	public abstract GenericJpaRepository<P, ID> repo();
	public abstract IEntityPOConvert<E, P> convert();
}
